EDA: Caracterización y distribución de los datos#

Líbrerias y modulos necesarios#

import json
import warnings
import pandas as pd
import plotly.express as px
warnings.filterwarnings('ignore')
import plotly.graph_objects as go
from urllib.request import urlopen
from funciones import grafico_datos_faltantes
from funciones import grafico_barras_categoricas

Conjunto de datos#

url = r"https://github.com/sePerezAlbor/Data/blob/main/data_analisis.xlsx?raw=true"
data = pd.read_excel(url, na_values = [" "])
data.head()
Nombre_Departamento Nombre_Municipio sitio_def año_def mes_def sexo estado_civil grupo_edad nivel_edu grupo_etnico ... seg_social ent_salud_cod prob_muerte cert_medica asistencia_med causa_directa causa_ant_1 causa_basica causa_OPS region
0 ANTIOQUIA MEDELLÍN Hospital o Clínica 2014 Julio Femenino Soltero 15-19 años Secundaria Ninguno ... Subsidiado EPS - Subsidiado Natural Historia clínica SI J969 C782 C509 Tumor Maligno Andina
1 ANTIOQUIA APARTADÓ Hospital o Clínica 1997 Agosto Femenino NaN 15-19 años NaN NaN ... NaN NaN NaN NaN NaN NaN NaN C509 NaN Andina
2 ANTIOQUIA ENVIGADO Casa 1986 Abril Femenino Soltero 15-19 años NaN NaN ... NaN NaN NaN NaN NaN NaN NaN 1749 NaN Andina
3 ATLÁNTICO BARRANQUILLA Hospital o Clínica 1989 Julio Femenino Casado 15-19 años NaN NaN ... NaN NaN NaN NaN NaN NaN NaN 1749 NaN Atlántica
4 ATLÁNTICO BARRANQUILLA Hospital o Clínica 1998 Agosto Femenino Casado 15-19 años Secundaria NaN ... NaN NaN Natural Historia clínica SI J969 C780 C509 Tumor Maligno Atlántica

5 rows × 21 columns

data.shape
(46869, 21)

Análisis general del conjunto de datos#

# Dividir en dos grupos (ajusta según necesidad)
categorical_columns = data.select_dtypes(include='object').columns
grupo1 = categorical_columns[:11]  # Primeras 12 columnas
grupo2 = categorical_columns[11:]  # Resto de columnas
display(data[grupo1].describe())
display(data[grupo2].describe())
Nombre_Departamento Nombre_Municipio sitio_def mes_def sexo estado_civil grupo_edad nivel_edu grupo_etnico depto_res seg_social
count 46869 46867 46208 46869 46869 44256 46869 29214 15590 46869 33403
unique 33 872 3 12 1 4 14 5 6 33 6
top BOGOTA DC BOGOTA DC Casa Mayo Femenino Casado 50-54 años Primaria Ninguno BOGOTA DC Contributivo
freq 10877 10877 24275 4095 46869 17585 8052 14445 14549 9686 17658
ent_salud_cod prob_muerte cert_medica asistencia_med causa_directa causa_ant_1 causa_basica causa_OPS region
count 15685 33970 32963 33268 33877 29066 46869 33970 46869
unique 5 2 4 3 398 615 17 1 4
top EPS Natural Historia clínica SI C509 C509 C509 Tumor Maligno Andina
freq 8497 33965 28059 30717 6843 8782 35166 33970 29448

Tratamiento de datos faltantes#

Note

En esta sección del proyecto (EDA), se analizarán los patrones de los valores faltantes previamente identificados en la fase de ETL. Se explorará su distribución a lo largo del dataset y su posible relación con otras variables, determinando si la ausencia de datos sigue algún patrón específico o es aleatoria. Además, se evaluarán diferentes estrategias de imputación, como el uso de la media, mediana, moda o modelos predictivos, según la naturaleza de cada variable. Este análisis permitirá minimizar el impacto de los datos faltantes y mejorar la calidad de las conclusiones obtenidas a partir del conjunto de datos.

Veamos primero, la cantidad de datos faltantes en general.

grafico_datos_faltantes(data)

Variable: Grupo étnico (grupo_etnico)#

display(data['grupo_etnico'].value_counts(dropna=False).to_frame())
count
grupo_etnico
NaN 31279
Ninguno 14549
Afrodescendiente 894
Indígena 87
Gitano 33
Raizal 21
Palenquero 6
data['grupo_etnico'] = data['grupo_etnico'].fillna('Ninguno')

Variable: Entidad de salud del paciente (ent_salud_cod)#

display(data['ent_salud_cod'].value_counts(dropna=False).to_frame())
count
ent_salud_cod
NaN 31184
EPS 8497
EPS - Subsidiado 6375
EESS 659
ESE 110
EAPB 44
data['ent_salud_cod'].fillna("No especificado", inplace=True)

Variable: Primera causa asociada a la defunción por antecendentes (causa_ant_1)#

display(data['causa_ant_1'].value_counts(dropna=False).to_frame())
count
causa_ant_1
NaN 17803
C509 8782
C780 3904
C80X 1192
C793 980
... ...
K291 1
I742 1
I311 1
J91 1
J188 1

616 rows × 1 columns

moda = data['causa_ant_1'].mode()[0]  
data['causa_ant_1'].fillna(moda, inplace=True)

Variable: Nivel de educación (nivel_edu)#

display(data['nivel_edu'].value_counts(dropna=False).to_frame())
count
nivel_edu
NaN 17655
Primaria 14445
Secundaria 8775
Superior 3417
Ninguno 2411
Preescolar 166
data['nivel_edu'].fillna("No especificado", inplace=True)

Variable: Tipo de certificación médica de la defunción (cert_medica)#

display(data['cert_medica'].value_counts(dropna=False).to_frame())
count
cert_medica
Historia clínica 28059
NaN 13906
Interrogatorio a familiares o testigos 4715
Necropsia 122
Pruebas de laboratorio 67
data['cert_medica'].fillna("No especificado", inplace=True)

Variable: Tipo de régimen de seguridad social (seg_social)#

display(data['seg_social'].value_counts(dropna=False).to_frame())
count
seg_social
Contributivo 17658
NaN 13466
Subsidiado 11095
Vinculado 2706
Otro 969
Particular 854
Ignorado 121
data['seg_social'].fillna("No especificado", inplace=True)

Variable: Causa directa de la defunción (causa_directa)#

display(data['causa_directa'].value_counts(dropna=False).to_frame())
count
causa_directa
NaN 12992
C509 6843
I469 4155
J969 4018
J960 3481
... ...
C969 1
K469 1
K570 1
I808 1
J952 1

399 rows × 1 columns

moda = data['causa_directa'].mode()[0]
data['causa_directa'].fillna(moda, inplace=True)

Variable: Asistencia médica durante la defunción (asistencia_med)#

display(data['asistencia_med'].value_counts(dropna=False).to_frame())
count
asistencia_med
SI 30717
NaN 13601
NO 2458
Ignorado 93
data['asistencia_med'].fillna("NO", inplace=True)
data['asistencia_med'].replace({"Ignorado": "NO"}, inplace=True)

Variable: Causa según clasificación OPS (causa_OPS)#

display(data['causa_OPS'].value_counts(dropna=False).to_frame())
count
causa_OPS
Tumor Maligno 33970
NaN 12899
data['causa_OPS'].fillna("Diferente a Tumor", inplace=True)

Variable: Tipo de causa muerte (prob_muerte)#

display(data['prob_muerte'].value_counts(dropna=False).to_frame())
count
prob_muerte
Natural 33965
NaN 12899
En estudio 5
data.drop('prob_muerte', axis=1, inplace=True)

Variable: Estado civil (estado_civil)#

display(data['estado_civil'].value_counts(dropna=False).to_frame())
count
estado_civil
Casado 17585
Soltero 10428
Viudo 9303
Unión Libre, Divorciado/Otro 6940
NaN 2613
data['estado_civil'].fillna("No especificado", inplace=True)

Variable: Sitio de defunción (sitio_def)#

display(data['sitio_def'].value_counts(dropna=False).to_frame())
count
sitio_def
Casa 24275
Hospital o Clínica 21425
NaN 661
Otro Sitio 508
moda = data['sitio_def'].mode()[0]
data['sitio_def'].fillna(moda, inplace=True)

Resultados de imputación#

grafico_datos_faltantes(data)
data.head()
Nombre_Departamento Nombre_Municipio sitio_def año_def mes_def sexo estado_civil grupo_edad nivel_edu grupo_etnico depto_res seg_social ent_salud_cod cert_medica asistencia_med causa_directa causa_ant_1 causa_basica causa_OPS region
0 ANTIOQUIA MEDELLÍN Hospital o Clínica 2014 Julio Femenino Soltero 15-19 años Secundaria Ninguno ANTIOQUIA Subsidiado EPS - Subsidiado Historia clínica SI J969 C782 C509 Tumor Maligno Andina
1 ANTIOQUIA APARTADÓ Hospital o Clínica 1997 Agosto Femenino No especificado 15-19 años No especificado Ninguno ANTIOQUIA No especificado No especificado No especificado NO C509 C509 C509 Diferente a Tumor Andina
2 ANTIOQUIA ENVIGADO Casa 1986 Abril Femenino Soltero 15-19 años No especificado Ninguno ANTIOQUIA No especificado No especificado No especificado NO C509 C509 1749 Diferente a Tumor Andina
3 ATLÁNTICO BARRANQUILLA Hospital o Clínica 1989 Julio Femenino Casado 15-19 años No especificado Ninguno ATLÁNTICO No especificado No especificado No especificado NO C509 C509 1749 Diferente a Tumor Atlántica
4 ATLÁNTICO BARRANQUILLA Hospital o Clínica 1998 Agosto Femenino Casado 15-19 años Secundaria Ninguno ATLÁNTICO No especificado No especificado Historia clínica SI J969 C780 C509 Tumor Maligno Atlántica
data.to_excel(r"C:\Users\kamac\OneDrive\Desktop\SeminarioInvestigativoUN\data_clean.xlsx", index=False)

Análisis Univariado#

grafico_barras_categoricas(data, 'Nombre_Departamento', 'Departamento', 'Cantidad de defunciones', 'Defunciones por departamento')
grafico_barras_categoricas(data, 'mes_def', 'Mes de defunción', 'Cantidad de defunciones', 'Defunciones por mes')
grafico_barras_categoricas(data, 'sitio_def', 'Sitio de defunción', 'Cantidad de registros', 'Distribución sitio de defunción')
grafico_barras_categoricas(data, 'estado_civil', 'Estado civil', 'Cantidad de registros', 'Distribución estado civil')
grafico_barras_categoricas(data, 'grupo_edad', 'Grupo de edad', 'Cantidad de registros', 'Distribución grupo de edad')
grafico_barras_categoricas(data, 'nivel_edu', 'Nivel educativo', 'Cantidad de registros', 'Distribución nivel educativo')
grafico_barras_categoricas(data, 'grupo_etnico', 'Grupo étnico', 'Cantidad de registros', 'Distribución grupo étnico')
grafico_barras_categoricas(data,'seg_social', 'Seguridad social', 'Cantidad de registros', 'Distribución seguridad social')
grafico_barras_categoricas(data, 'ent_salud_cod', 'Entidad de salud', 'Cantidad de registros', 'Distribución entidad de salud')
grafico_barras_categoricas(data, 'cert_medica', 'Certificación médica', 'Cantidad de registros', 'Distribución certificación médica')
grafico_barras_categoricas(data, 'asistencia_med', 'Asistencia médica', 'Cantidad de registros', 'Distribución asistencia médica')
grafico_barras_categoricas(data, 'causa_basica', 'Causa básica', 'Cantidad de registros', 'Distribución causa básica')
grafico_barras_categoricas(data, 'causa_OPS', 'Causa OPS', 'Cantidad de registros', 'Distribución causa OPS')
grafico_barras_categoricas(data,'region', 'Región', 'Cantidad de registros', 'Distribución región')

Tabla de caracterización#

image.png

Análisis Bivariado#

Tendencia de defunciones por año y región#

data_grouped = data.groupby(['año_def', 'region']).size().reset_index(name='Defunciones')

color_palette = ["#ffb6c1", "#ff69b4", "#db7093", "#c71585", 
                 "#ff1493", "#e60073", "#cc0033", "#b20000"]

fig6 = px.line(data_grouped, 
               x='año_def', 
               y='Defunciones', 
               color='region',
               title='Tendencia de Defunciones por Año y Región',
               markers=True,  # Agregar marcadores
               color_discrete_sequence=color_palette)  # Aplicar paleta personalizada

fig6.update_layout(
    font=dict(family="Bahnschrift", color="black"),  # Fuente Bahnschrift en negro
    xaxis_title="Año de Defunción", 
    yaxis_title="Número de Defunciones",
    legend_title="Región",  # Título de la leyenda
    template="plotly_white"  # Fondo blanco para mayor claridad
)

fig6.show()

Distribución de Defunciones por Grupo de Edad y Estado Civil#

color_palette = ["#ffb6c1", "#ff69b4", "#db7093", "#c71585", 
                 "#ff1493", "#e60073", "#cc0033", "#b20000"]

fig = px.box(data, 
             x="estado_civil", 
             y="grupo_edad", 
             title="Distribución de Defunciones por Grupo de Edad y Estado Civil",
             color="estado_civil",
             color_discrete_sequence=color_palette,
             labels={"estado_civil": "Estado Civil"})

fig.update_layout(
    font=dict(family="Bahnschrift", color="black"),  # Fuente en negro
    xaxis_title="Estado Civil", 
    yaxis_title="Grupo de Edad",
    width=1100,  # Ajustar ancho
    height=800   # Ajustar alto
)

fig.show()

Distribución de Edad de Defunciones por Año#

color_palette = ["#ffb6c1", "#ff69b4", "#db7093", "#c71585", 
                 "#ff1493", "#e60073", "#cc0033", "#b20000", 
                 "#990000", "#800000", "#ff3366", "#ff0033", 
                 "#d40000", "#a10000"]

fig = px.box(data, 
             x="año_def", 
             y="grupo_edad", 
             title="Distribución de Edad de Defunciones por Año",
             color="grupo_edad",
             color_discrete_sequence=color_palette, labels={"grupo_edad": 'Grupo de edad'}) 
fig.update_layout(
    font=dict(family="Bahnschrift", color="black"),
    xaxis_title="Año de Defunción", 
    yaxis_title="Grupo de Edad",
    width=1200,  # Ancho del gráfico
    height=1000   # Alto del gráfico
    
)

fig.show()

Evolución de las Defunciones según el Lugar de Muerte#

# Agrupar datos por año de defunción y sitio de defunción
data_grouped = data.groupby(["año_def", "sitio_def"]).size().reset_index(name="count")

# Gráfico de líneas con tonalidades rosadas
fig = px.line(data_grouped, 
              x="año_def", 
              y="count", 
              color="sitio_def",
              markers=True,  # Agregar puntos en la línea
              title="Evolución de las Defunciones según el Lugar de Muerte",
              color_discrete_sequence=["#ffb6c1", "#ff69b4", "#db7093", "#c71585"])  # Tonos rosados

fig.update_layout(
    font=dict(family="Bahnschrift", color="black"),
    xaxis_title="Año de Defunción", 
    yaxis_title="Número de Defunciones",
    legend_title="Lugar de Defunción",
    template="plotly_white"
)

fig.show()

Relación entre Nivel Educativo y Tipo de Seguridad Social#

# Agrupar datos por nivel educativo y tipo de seguridad social
data_grouped = data.groupby(["nivel_edu", "seg_social"]).size().reset_index(name="count")

# Convertir a formato de matriz (pivot)
heatmap_data = data_grouped.pivot(index="nivel_edu", columns="seg_social", values="count")

# Crear heatmap con tonalidades rosadas
fig = px.imshow(heatmap_data, 
                labels=dict(x="Seguridad Social", y="Nivel Educativo", color="Cantidad"),
                title="Relación entre Nivel Educativo y Tipo de Seguridad Social",
                color_continuous_scale=["#ffb6c1", "#ff69b4", "#db7093", "#c71585"])

fig.update_layout(
    font=dict(family="Bahnschrift", color="black"),
    xaxis_title="Tipo de Seguridad Social", 
    yaxis_title="Nivel Educativo",
     template="plotly_white"
)

fig.show()

Evolución de Defunciones por Grupo de Edad#

data_grouped = data.groupby(["año_def", "grupo_edad"]).size().reset_index(name="count")
color_palette = ["#ffb6c1", "#ff69b4", "#db7093", "#c71585", 
                 "#ff1493", "#e60073", "#cc0033", "#b20000", 
                 "#990000", "#800000", "#ff3366", "#ff0033", 
                 "#d40000", "#a10000"]
fig = px.line(data_grouped, 
              x="año_def", 
              y="count", 
              color="grupo_edad", 
              title="Evolución de Defunciones por Grupo de Edad",
              markers=True,  # Agregar puntos en la línea
              color_discrete_sequence=color_palette,  # Aplicar paleta de colores
              labels={"grupo_edad": "Grupo de Edad", "año_def": "Año", "count": "Número de Defunciones"})  

fig.update_layout(
    font=dict(family="Bahnschrift", color="black"),  # Fuente en negro
    xaxis_title="Año de Defunción", 
    yaxis_title="Número de Defunciones",
    legend_title="Grupo de Edad",  
    width=1200,  
    height=800,     
    template="plotly_white"
)

fig.show()

Análisis de casos a lo largo del tiempo#

meses_map = {
    "Enero": "01", "Febrero": "02", "Marzo": "03", "Abril": "04", "Mayo": "05", "Junio": "06",
    "Julio": "07", "Agosto": "08", "Septiembre": "09", "Octubre": "10", "Noviembre": "11", "Diciembre": "12"
}

data['mes_def'] = data['mes_def'].map(meses_map)
data['Fecha'] = data['año_def'].astype(str) + '-' + data['mes_def']

conteo_fechas = data['Fecha'].value_counts().reset_index()
conteo_fechas.columns = ['Fecha', 'Cantidad']
conteo_fechas = conteo_fechas.sort_values('Fecha')
conteo_fechas['Fecha'] = pd.to_datetime(conteo_fechas['Fecha'], format='%Y-%m')

fig = px.line(conteo_fechas, 
              x='Fecha', 
              y='Cantidad',
              title="Casos de Defunción a lo Largo del Tiempo",
              labels={'Cantidad': 'Número de Defunciones', 'Fecha': 'Fecha'},
              markers=True)

fig.update_traces(line=dict(color="#C2185B"),  
                  marker=dict(color="#F06292", size=8))  

fig.update_layout(
    template="plotly_white",
    xaxis_title="Fecha",
    yaxis_title="Número de Defunciones",
    font=dict(family="Bahnschrift", size=14, color="black"),
    title_font=dict(family="Bahnschrift", size=22, color="black"),
    plot_bgcolor="white",
    paper_bgcolor="white"
)

fig.show()

Mapa de casos por departamento#

with urlopen('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/Colombia.geo.json') as response:
    counties = json.load(response)
    
locs=[]; z_id=[];
for loc in counties['features']:
    loc['id'] = loc['properties']['NOMBRE_DPT']
    locs.append(loc['properties']['NOMBRE_DPT'])
    z_id.append(loc['properties']['HECTARES'])
colorscale = [
    [0, "#fde0dc"],  # Rosa claro
    [0.2, "#f9bdbb"],  
    [0.4, "#f69988"],  
    [0.6, "#f36c60"],  
    [0.8, "#e84e40"],  
    [1, "#d50000"]  # Rojo intenso
]

# Contar la cantidad de casos por departamento
casos_por_departamento = data['Nombre_Departamento'].value_counts().reset_index()
casos_por_departamento.columns = ['Nombre_Departamento', 'Casos']

# Reemplazar nombres para que coincidan con el GeoJSON
correccion_nombres = {
    'ATLÁNTICO': 'ATLANTICO',
    'BOLÍVAR': 'BOLIVAR',
    'BOGOTA DC': 'SANTAFE DE BOGOTA D.C',
    'BOYACÁ': 'BOYACA',
    'CÓRDOBA': 'CORDOBA',
    'CAQUETÁ': 'CAQUETA',
    'CHOCÓ': 'CHOCO',
    'LA GUAJIRA': 'LA GUAJIRA',
    'ARCHIPIÉLAGO DE SAN ANDRÉS Y PROVIDENCIA Y SANTA CATALINA': 'ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA',
    'GUAINÍA': 'GUAINIA',
    'GUAVIARE': 'GUAVIARE',
    'VAUPÉS': 'VAUPES',
    'VICHADA': 'VICHADA'
}

casos_por_departamento['Nombre_Departamento'] = casos_por_departamento['Nombre_Departamento'].replace(correccion_nombres)

locs = casos_por_departamento['Nombre_Departamento'].tolist()
z_id = casos_por_departamento['Casos'].tolist()

fig = go.Figure(go.Choroplethmapbox(
                    geojson=counties,
                    locations=locs,
                    z=z_id,
                    colorscale= colorscale,
                    colorbar_title="Casos de Defunción"))

fig.update_layout(mapbox_style="open-street-map",
                  mapbox_zoom=4,
                  mapbox_center={"lat": 4.570868, "lon": -74.2973328}  ,width=1200,  
    height=800 )

fig.show()

Pendientes#

  • Resolver dudas faltantes.

  • Incluir en los análisis las causas CIE-10

  • Interpretar

  • Correción de mapa para que aparezca San Andres